﻿using System;
using System.Windows;
using System.Threading;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using PlottingBasics.LINQHelpers;

namespace ProbabilityNot
{
    /// <summary>
    /// Temp holding of a distribution that is availible to all threads. When we get one in, we copy
    /// it over to the GUI thread. If a "new" verison comes in, then attempt to update it automatically.
    /// Don't update needlessly (if too many come in, in a row).
    /// </summary>
    public class DistributionCopier
    {
        /// <summary>
        /// The internal store
        /// </summary>
        private Point[] _points = null;

        /// <summary>
        /// Get/Set the array of items we are keeping locally. We don't notice if you change
        /// individual elements - just if the whole thing changes!
        /// </summary>
        public Point[] Local
        {
            get { return _points; }
            set { 
                _points = value;
                BeginUpdate();
            }
        }

        /// <summary>
        /// Get or set the amount that Y has to change by in the array for it to be emitted.
        /// </summary>
        public double Tolerance { get; set; }

        /// <summary>
        /// How many ticks should we wait before we do an update to the GUI? New updates mean that
        /// this won't be taken into account. This uses a thread, so keep it "short".
        /// </summary>
        public int DelayTicks { get; set; }

        /// <summary>
        /// The defualt conversion back to a series of points. Makes use in code "simple".
        /// </summary>
        /// <param name="cp"></param>
        /// <returns></returns>
        public static implicit operator Point[](DistributionCopier cp)
        {
            return cp.Local;
        }

        /// <summary>
        /// The watermark number - keeps track of what version of update this is.
        /// </summary>
        private int _watermark = 0;

        /// <summary>
        /// Track the dependency info that we are using
        /// </summary>
        private DependencyProperty _dependencyProperty;
        private DependencyObject _dependencyObject;

        /// <summary>
        /// Create this GUI copier and have it set the property and object as specified.
        /// </summary>
        /// <param name="dep"></param>
        /// <param name="prop"></param>
        public DistributionCopier(DependencyObject dep, DependencyProperty prop)
        {
            _dependencyObject = dep;
            _dependencyProperty = prop;
            Tolerance = 0.0;
            DelayTicks = 0;
        }

        /// <summary>
        /// Create this GUI copier and have it set the property and object as specified.
        /// </summary>
        /// <param name="dep"></param>
        /// <param name="prop"></param>
        public DistributionCopier(DependencyObject dep, DependencyProperty prop, double tol)
        {
            _dependencyObject = dep;
            _dependencyProperty = prop;
            Tolerance = tol;
            DelayTicks = 0;
        }

        /// <summary>
        /// We have a new points array. We now start a thing off to update it.
        /// Just in case something new comes in in the mean time, we water mark it so
        /// that we can be sure to skip any updates that come along while we are waiting for this
        /// to fire off.
        /// </summary>
        private void BeginUpdate()
        {
            _watermark++;
            ThreadPool.QueueUserWorkItem(obj => UpdateGUIPointsArray(_watermark, _points));
        }

        /// <summary>
        /// Get the sequence ready for updating.
        /// </summary>
        /// <param name="_watermark"></param>
        /// <param name="_points"></param>
        /// <returns></returns>
        private void UpdateGUIPointsArray(int watermark, Point[] points)
        {
            ///
            /// Are we going ot delay and see if there was an update?
            /// 

            if (DelayTicks > 0)
            {
                Thread.Sleep(DelayTicks);
            }

            ///
            /// Why waste time if there is another updating coming?
            /// 

            if (watermark != _watermark)
            {
                return;
            }

            ///
            /// Thin out the points array according to our tollerance.
            /// 

            IEnumerable<Point> pts = points;
            if (Tolerance > 0.0)
            {
                pts = points.Thin((lastOne, thisOne) => Math.Abs(lastOne.Y - thisOne.Y) < Tolerance);
            }

            ///
            /// Translate over to an observable collection
            /// 

            ObservableCollection<Point> cp = new ObservableCollection<Point>();
            foreach (var p in pts)
            {
                cp.Add(p);
            }

            ///
            /// Finally, flash over to the GUI thread and do our work
            /// 

            _dependencyObject.Dispatcher.BeginInvoke(() =>
            {
                if (_watermark != watermark)
                {
                    return;
                }
                _dependencyObject.SetValue(_dependencyProperty, cp);
            });
        }

    }
}
